package com.xc.file.service.impl;

import com.xc.file.api.NoticeApi;
import com.xc.file.bean.HashBean;
import com.xc.file.bean.IndexBean;
import com.xc.file.bean.UploadBean;
import com.xc.file.config.Constants;
import com.xc.file.config.StartSuccess;
import com.xc.file.dto.UploadDto;
import com.xc.file.enums.FailCode;
import com.xc.file.exception.OperateException;
import com.xc.file.exception.ServerException;
import com.xc.file.info.FileInfo;
import com.xc.file.info.FolderInfo;
import com.xc.file.service.UploadFileService;
import com.xc.tool.utils.FileUtils;
import com.xc.tool.utils.Md5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.management.OperationsException;
import javax.management.openmbean.OpenDataException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

/**
 * <p>项目名称: Stars</p>
 * <p>描述: 上传文件实现</p>
 * <P>创建人: 小程</p>
 * <P>创建人邮箱: 779943476@qq.com</p>
 * <p>创建时间: 2019/8/14</p>
 *
 * @version v1.0
 */
@Service
public class UploadFileServiceImpl implements UploadFileService {

    @Autowired
    private Constants constants;
    @Autowired
    private StartSuccess startSuccess;
    @Autowired
    private NoticeApi noticeApi;

    @Override
    public UploadDto uploadFile(MultipartFile file, UploadBean uploadBean) {
        //判断文件是否需要续传
        if (uploadBean.getSize() == file.getSize()) {
            //不需要续传
            return singleUpload(file, uploadBean);
        } else {
            //判断文件大小有没有小于，每次上传文件的大小
            if (uploadBean.getSize() < constants.getRenewalSize()) {
                //小于，重新上传，renewalSize设置为文件大小
                UploadDto uploadDto = new UploadDto();
                uploadDto.setFinish(false);
                uploadDto.setFileIndex(0l);
                uploadDto.setRenewalSize(uploadBean.getSize());
                return uploadDto;
            }
            String hashCode = uploadBean.getHashCode();
            try {
                lock(hashCode);
                return subsectionUpload(file, uploadBean);
            } finally {
                unlock(hashCode);
            }

        }
    }

    /**
     * 单个文件上传
     *
     * @param file       文件信息
     * @param uploadBean 上传信息
     * @return 返回参数
     */
    private UploadDto singleUpload(MultipartFile file, UploadBean uploadBean) {
        String hashCode = uploadBean.getHashCode();
        UploadDto uploadDto = new UploadDto(constants.getRenewalSize());
        //检测文件是否上传
        String path = verifyFileExist(hashCode, constants.getSuffix());
        if (path != null) {
            return verifyFile(uploadDto, path, uploadBean);
        }
        //获取可以上传的目录
        path = getFilePath(uploadBean.getSize());
        //拼接上传路径
        String filePath = path + File.separator + hashCode + constants.getSuffix();
        //上传文件
        uploadFileHandle(filePath, uploadBean, file, uploadBean.getSize());
        //文件上传完成
        File newFile = new File(filePath);
        //计算文件hash值
        String md5 = Md5Utils.getFileMd5(newFile);
        if (!hashCode.equals(md5)) {
            FileUtils.delete(filePath);
            throw FailCode.FILE_DATA_ERROR.getOperateException();
        }
        //上传成功通知管理服务
        String fid = noticeSuccess(path, newFile.length(), uploadBean, filePath);
        uploadDto.setFid(fid);
        //返回参数
        uploadDto.setFileIndex(newFile.length());
        uploadDto.setFinish(true);
        uploadDto.setHashCode(hashCode);
        return uploadDto;
    }

    /**
     * 分段上传
     *
     * @param file       文件信息
     * @param uploadBean 上传信息
     * @return 返回参数
     */
    private UploadDto subsectionUpload(MultipartFile file, UploadBean uploadBean) {
        String hashCode = uploadBean.getHashCode();
        UploadDto uploadDto = new UploadDto(constants.getRenewalSize());
        //判断源文件是否存在
        String path = verifyFileExist(hashCode, constants.getSuffix());
        if (path != null) {
            return verifyFile(uploadDto, path, uploadBean);
        }
        //判断临时文件是否存在
        path = verifyFileExist(hashCode, constants.getUploadSuffix());
        if (path == null) {
            //删除文件信息和文件
            deleteFileAndIndex(hashCode);
            //创建临时文件信息
            uploadDto = createIndex(uploadBean);
            if (uploadDto != null) {
                return uploadDto;
            } else {
                throw FailCode.FILE_INFO_DATA_ERROR.getServerException();
            }
        }
        FileInfo info = constants.getFileInfoMap().get(hashCode);
        if (info == null) {
            //判断源文件是否存在
            String newPath = verifyFileExist(hashCode, constants.getSuffix());
            if (newPath != null) {
                return verifyFile(uploadDto, path, uploadBean);
            } else {
                throw FailCode.FILE_NOT_EXIST.getServerException();
            }
        }
        String tempPath = info.getFilePath() + File.separator + hashCode + constants.getUploadSuffix();
        if (info.getIsUpload()) {
            //上传完成
            File tempFile = new File(tempPath);
            //计算文件hash值
            String md5 = Md5Utils.getFileMd5(tempFile);
            //验证文件md5是否正确
            if (hashCode.equals(md5)) {
                return uploadSuccess(uploadDto, uploadBean, path);
            } else {
                //删除文件信息和文件
                deleteFileAndIndex(hashCode);
                throw FailCode.FILE_DATA_ERROR.getOperateException();
            }
        }
        //上传文件
        uploadFileHandle(tempPath, uploadBean, file, uploadBean.getSize());
        int i = 0;
        for (IndexBean indexBean : info.getIndex()) {
            if (indexBean.getStartIndex().equals(uploadBean.getFileIndex())) {
                indexBean.setState(true);
            }
            if (indexBean.getState()) {
                i++;
            }
        }
        //判断是不是最后一次上传
        if (i == info.getIndex().size()) {
            if (!info.getIsUpload()) {
                info.setIsUpload(true);
            }
            uploadDto.setFinish(false);
            uploadDto.setFileIndex(0l);
            return uploadDto;
        } else {
            IndexBean indexBean = getIndex(hashCode);
            if (indexBean != null) {
                uploadDto.setFileIndex(indexBean.getStartIndex());
                uploadDto.setFinish(false);
                return uploadDto;
            } else {
                //判断源文件是否存在
                String newPath = verifyFileExist(hashCode, constants.getSuffix());
                if (newPath != null) {
                    return verifyFile(uploadDto, path, uploadBean);
                } else {
                    throw FailCode.FILE_NOT_EXIST.getServerException();
                }
            }
        }
    }

    private static Map<String, Long> lockMap = new Hashtable<>();

    /**
     * 加锁,30分钟超时
     *
     * @param key 锁的对象
     * @return 返回是否获取到锁，true：获取到锁，false：没获取到锁
     */
    private boolean lock(String key) {
        synchronized ("") {
            while (true) {
                Long lock = lockMap.get(key);
                long currentTime = System.currentTimeMillis();
                if (lock == null) {
                    lockMap.put(key, currentTime);
                    return true;
                } else if ((currentTime - lock) > 1800000) {
                    throw FailCode.UPLOAD_FILE_OVERTIME.getOperateException();
                }
            }
        }
    }

    /**
     * 删除锁
     *
     * @param key 锁的对象
     */
    private void unlock(String key) {
        lockMap.remove(key);
    }

    /**
     * 文件上传完成通知
     *
     * @param uploadDto  返回信息
     * @param path       文件存放位置
     * @param uploadBean 上传信息
     * @return 返回信息
     */
    private UploadDto verifyFile(UploadDto uploadDto, String path, UploadBean uploadBean) {
        String hashCode = uploadBean.getHashCode();
        String tempPath = path + File.separator + hashCode + constants.getUploadSuffix();
        File tempFile = new File(tempPath);
        if (tempFile.exists()) {
            FileUtils.delete(tempPath);
        }
        //上传完成
        String newPath = path + File.separator + hashCode + constants.getSuffix();
        File newFile = new File(newPath);
        if (newFile.exists()) {
            //上传成功通知管理服务
            String fid = noticeSuccess(path, newFile.length(), uploadBean, null);
            uploadDto.setFid(fid);
            //返回参数
            uploadDto.setFileIndex(newFile.length());
            uploadDto.setFinish(true);
            uploadDto.setHashCode(hashCode);
            return uploadDto;
        } else {
            throw FailCode.FILE_NOT_EXIST.getServerException();
        }
    }

    /**
     * 上传成功处理
     *
     * @param uploadDto  返回信息
     * @param uploadBean 上传信息
     * @param path       存放路径
     * @return 返回成功信息
     */
    private UploadDto uploadSuccess(UploadDto uploadDto, UploadBean uploadBean, String path) {
        String hashCode = uploadBean.getHashCode();
        String tempPath = path + File.separator + hashCode + constants.getUploadSuffix();
        //重命名文件
        String newPath = path + File.separator + hashCode + constants.getSuffix();
        File tempFile = new File(tempPath);
        //上传成功通知管理服务
        String fid = noticeSuccess(path, tempFile.length(), uploadBean, null);
        uploadDto.setFid(fid);
        //重命名
        File newFile = new File(newPath);
        if (!newFile.exists()) {
            if (!tempFile.renameTo(newFile)) {
                //删除文件信息和文件
                deleteFileAndIndex(hashCode);
                throw FailCode.RENAME_FAIL.getServerException();
            }
        } else {
            //删除文件信息和文件
            deleteFileAndIndex(hashCode);
            if (constants.getOpenNotice()) {
                startSuccess.notice();
                throw FailCode.FILE_EXIST.getServerException();
            }
        }
        //删除文件信息和临时文件
        deleteFileAndIndex(hashCode);
        //返回参数
        uploadDto.setFileIndex(newFile.length());
        uploadDto.setFinish(true);
        uploadDto.setHashCode(hashCode);
        return uploadDto;
    }

    /**
     * 上传成功通知
     *
     * @param path       路径
     * @param uploadBean 上传信息
     * @param size       文件大小
     * @return 文件id
     */
    private String noticeSuccess(String path, Long size, UploadBean uploadBean, String newPath) {
        if (constants.getOpenNotice()) {
            try {
                HashBean hashBean = new HashBean(constants.getAppId(), constants.getAppSecret());
                hashBean.setFid(uploadBean.getUploadId());
                hashBean.setSize(size);
                hashBean.setHashCode(uploadBean.getHashCode());
                hashBean.setFilePath(path);
                return noticeApi.noticeSuccess(hashBean);
            } catch (OperateException | ServerException e) {
                if (newPath != null) {
                    FileUtils.delete(newPath);
                }
                throw e;
            } catch (Exception e) {
                throw e;
            }
        }
        return null;
    }

    /**
     * 创建文件信息
     *
     * @param uploadBean 上传信息
     * @return 返回临时文件路径
     */
    private UploadDto createIndex(UploadBean uploadBean) {
        String hashCode = uploadBean.getHashCode();
        String path = getFilePath(uploadBean.getSize());
        long size = uploadBean.getSize();
        FileInfo info = new FileInfo();
        //计算数据段
        List<IndexBean> indexBeans = new ArrayList<>();
        long len = (size - (size % constants.getRenewalSize())) / constants.getRenewalSize();
        for (int i = 0; i < len; i++) {
            IndexBean indexBean = new IndexBean();
            long start = i * constants.getRenewalSize();
            indexBean.setStartIndex(start);
            indexBean.setStopIndex(start + constants.getRenewalSize());
            indexBeans.add(indexBean);
        }
        //剩余的数据段
        IndexBean indexBean = new IndexBean();
        indexBean.setStartIndex(size - (size % constants.getRenewalSize()));
        indexBean.setStopIndex(size);
        indexBeans.add(indexBean);
        //文件信息
        info.setIndex(indexBeans);
        info.setSize(size);
        info.setFilePath(path);
        String tempPath = path + File.separator + hashCode + constants.getUploadSuffix();
        File tempFile = new File(tempPath);
        if (tempFile.exists()) {
            FileUtils.delete(tempPath);
        }
        //创建文件
        try {
            if (!tempFile.createNewFile()) {
                throw FailCode.TEMP_FILE_CREATE_FAIL.getServerException();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        constants.getFileInfoMap().put(hashCode, info);
        //获取临时文件信息
        UploadDto uploadDto = new UploadDto(constants.getRenewalSize());
        for (IndexBean index : info.getIndex()) {
            if (!index.getState()) {
                uploadDto.setFinish(false);
                uploadDto.setFileIndex(index.getStartIndex());
                return uploadDto;
            }
        }
        return null;
    }

    /**
     * 获取index信息
     *
     * @param hashCode 文件hash值
     * @return index信息
     */
    private IndexBean getIndex(String hashCode) {
        FileInfo info = constants.getFileInfoMap().get(hashCode);
        for (IndexBean indexBean : info.getIndex()) {
            if (!indexBean.getState()) {
                return indexBean;
            }
        }
        return null;
    }

    /**
     * 删除文件信息和文件
     *
     * @param hashCode 文件的hash值
     */
    private void deleteFileAndIndex(String hashCode) {
        //删除文件源信息
        constants.getFileInfoMap().remove(hashCode);
        //删除文件
        List<FolderInfo> folderPath = constants.getFolderPaths();
        for (FolderInfo folderInfo : folderPath) {
            String path = folderInfo.getPath() + File.separator + hashCode + constants.getUploadSuffix();
            File file = new File(path);
            if (file.exists()) {
                FileUtils.delete(path);
            }
        }
    }


    @Override
    public void deleteFile(List<String> hashCodes) {
        for (String hashCode : hashCodes) {
            List<String> list = getFilePath(hashCode);
            for (String path : list) {
                File file = new File(path);
                if (!file.delete()) {
                    throw FailCode.DELETE_FILE_FAIL.getServerException();
                }
            }
        }
    }

    /**
     * 更具磁盘大小获取文件存放地址
     * 检索合适的存放位置
     *
     * @return 文件存放地址
     */
    private String getFilePath(Long size) {
        long freeSpace = 0;
        String path = null;
        for (FolderInfo folderInfo : constants.getFolderPaths()) {
            File file = new File(folderInfo.getPath());
            if (!file.exists() && !file.isDirectory()) {
                if (!file.mkdirs()) {
                    throw FailCode.CREATE_INIT_FOLDER.getServerException();
                }
            }
            if ((file.getFreeSpace() - folderInfo.getReserveSpace()) >= size) {
                if (freeSpace < file.getFreeSpace()) {
                    freeSpace = file.getFreeSpace();
                    path = folderInfo.getPath();
                }
            }
        }
        if (path == null) {
            throw FailCode.INSUFFICIENT_SPACE.getServerException();
        }
        return path;
    }

    /**
     * 获取文件路径集合
     *
     * @param hashCode 文件hash值
     * @return 如果存在多个文件全部返回
     */
    private List<String> getFilePath(String hashCode) {
        List<FolderInfo> folderPath = constants.getFolderPaths();
        List<String> filePaths = new ArrayList<>();
        for (FolderInfo folderInfo : folderPath) {
            String path = folderInfo.getPath() + File.separator + hashCode + constants.getSuffix();
            File file = new File(path);
            if (file.exists()) {
                filePaths.add(path);
            }
        }
        return filePaths;
    }

    /**
     * 验证文件是否存在
     *
     * @param hashCode 文件hash值
     * @return true：存在，false：不存在
     */
    private String verifyFileExist(String hashCode, String suffix) {
        List<FolderInfo> folderPath = constants.getFolderPaths();
        String path = null;
        for (FolderInfo folderInfo : folderPath) {
            File file = new File(folderInfo.getPath() + File.separator + hashCode + suffix);
            if (file.exists()) {
                path = folderInfo.getPath();
            }
        }
        return path;
    }

    /**
     * 文件分段上传
     *
     * @param path       路径
     * @param uploadBean 上传参数
     * @param file       文件数据
     */
    private void uploadFileHandle(String path, UploadBean uploadBean, MultipartFile file, Long size) {
        RandomAccessFile loOutput = null;
        File uploadFile = new File(path);
        try {
            loOutput = new RandomAccessFile(uploadFile, "rws");
            loOutput.seek(uploadBean.getFileIndex());
            InputStream in = file.getInputStream();
            //<2>创建缓存大小
            byte[] buffer = null;
            if (file.getSize() == uploadBean.getSize()) {
                buffer = new byte[(int) file.getSize()]; // 1KB
            } else {
                if ((uploadBean.getFileIndex() + constants.getRenewalSize()) >= size) {
                    buffer = new byte[(int) file.getSize()]; // 1KB
                } else {
                    buffer = new byte[constants.getCacheSize()]; // 1KB
                }
            }
            //每次读取到内容的长度
            //<3>开始读取输入流中的内容
            while ((in.read(buffer)) != -1) { //当等于-1说明没有数据可以读取了
                loOutput.write(buffer);   //把读取到的内容写到输出流中
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (loOutput != null) {
                try {
                    loOutput.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

}
